Pinvon's Blog

所见, 所闻, 所思, 所想

Go实战(五) 函数

函数声明

函数的声明形式如下:

func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
    //这里是处理逻辑代码
    //返回多个值
    return value1, value2
}

其中, 函数返回的变量和类型中, 只写类型不写变量也是可以的. 如果返回值有多个, 需要用括号括起来, 只有一个则不用括号.

例子:

package main

import "fmt"

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func main() {
    x := 3
    y := 4
    z := 5
    maxxy := max(x, y)
    maxxz := max(x, z)
    fmt.Printf("max(%d, %d) = %d\n", x, y, maxxy)
    fmt.Printf("max(%d, %d) = %d\n", x, z, maxxz)
}

多个返回值

package main

import "fmt"

func SumAndProduct(a, b int) (int, int) {
    return a + b, a * b
}

func main() {
    x := 10
    y := 2
    sum, product := SumAndProduct(x, y)
    fmt.Printf("sum is %d\n", sum)
    fmt.Printf("product is %d\n", product)
}

注意, 有多个返回值时, 函数声明时, 返回值列表要用括号括起来.

可变参数

拥有可变参数的函数声明形式:

func myFunc(arg...int) {}

例子:

package main

import "fmt"

func sum(vals ...int) int {
    total := 0
    for _, val := range vals {
        total += val
    }
    return total
}

func main() {
    fmt.Printf("sum is %d\n", sum(1, 2))
    fmt.Printf("sum is %d\n", sum(1, 2, 3))
}

在函数体中, vals 是一个 slice.

传值与传指针

传值

Go 里面给函数传递参数的形式是值传递, 所以在函数内部对参数的修改, 不影响函数外部的值. 如:

package main

import "fmt"

func sum(a, b int) int {
    a = a + 1
    return a + b
}

func main() {
    x := 2
    y := 3
    fmt.Printf("x is %d\n", x)          // 2

    total := sum(x, y)

    fmt.Printf("total is %d\n", total)  // 5
    fmt.Printf("x is %d\n", x)          // 2
}

可以看到, 虽然在函数 sum() 内部, 对第一个参数进行了加 1, 但是在函数外部, x 的值仍然是 2, 而不是 3. 因为在调用函数 sum() 时, 接收的参数其实是 x 的拷贝.

传指针

变量在内存中是存放在某个地址上的, 如果有这个地址, 就能修改这个值. 上面的例子中, 修改不了的原因, 是因为值一样, 但地址不同, 修改的是另一个地址的值. 例子如下:

package main

import "fmt"

func sum(a *int, b int) int {
    *a = *a + 1
    return *a + b
}

func main() {
    x := 2
    y := 3
    fmt.Printf("x is %d\n", x)          // 2

    total := sum(&x, y)

    fmt.Printf("total is %d\n", total)  // 6
    fmt.Printf("x is %d\n", x)          // 3
}

可以看到, 通过传递指针, 实现了传递的数是同一个数.

传指针的好处很多. 如, 传指针可以提高程序的效率, 如果有一个体积很大的结构体, 传值相当于一次拷贝, 会浪费很多内存; 传指针可以使多个函数可以操作同一个对象.

Go 语言中的 channel, slice, map 的实现类似指针, 可以直接传递, 不用先取地址, 但是如果要改变 slice 的长度, 仍需先取地址.

defer

defer 是 Go 的关键字, defer 后面跟着的语句, 会在函数返回之前调用. 如:

func test() int {
    fmt.Println("test 0")
    defer fmt.Println("test 1")
    fmt.Println("test 2")
    return 0
}

// test 0
// test 2
// test 1

defer 常用于资源的释放. 如, 当打开某些资源时, 遇到错误要提前返回, 但是在返回前, 需要先关闭对应的资源, 否则会造成资源泄露等问题. 一般的写法如下:

func ReadWrite() bool {
    file.Open("file")
// 做一些工作
    if failureX {
        file.Close()
        return false
    }

    if failureY {
        file.Close()
        return false
    }

    file.Close()
    return true
}

可以看出, 上面的程序里, 有很多重复的代码 file.Close(). 使用 defer 可以改善程序结构:

func ReadWrite() bool {
    file.Open("file")
    defer file.Close()
    if failureX {
        return false
    }
    if failureY {
        return false
    }
    return true
}

如果有多个 defer 语句, 则后进先出. 如:

for i := 0; i < 6; i++ {
    defer fmt.Println(i)
}
// 5 4 3 2 1 0

函数作为值和类型

在 Go 中, 函数也是一种变量, 可以通过 type 来定义它, 在定义时会写明参数和返回值, 如果某个函数的参数和返回值与它相同, 就是同一个类型. 如:

package main

import "fmt"

type testInt func(int) bool // 声明了一个函数类型

func isOdd(integer int) bool {
	if integer%2 == 0 {
		return false
	}
	return true
}

func isEven(integer int) bool {
	if integer%2 == 0 {
		return true
	}
	return false
}

// 声明的函数类型在这个地方当做了一个参数

func filter(slice []int, f testInt) []int {
	var result []int
	for _, value := range slice {
		if f(value) {
			result = append(result, value)
		}
	}
	return result
}

func main(){
	slice := []int {1, 2, 3, 4, 5, 7}
	fmt.Println("slice = ", slice)
	odd := filter(slice, isOdd)    // 函数当做值来传递了
	fmt.Println("Odd elements of slice are: ", odd)
	even := filter(slice, isEven)  // 函数当做值来传递了
	fmt.Println("Even elements of slice are: ", even)
}

函数的这种用法对于编写通用接口非常有用, 我们可以使用相同类型的参数和返回值, 编写不同的逻辑, 使得程序变得更加灵活.

Panic 和 Recover

Comments

使用 Disqus 评论
comments powered by Disqus